package edu.neu.swc.skynet.service.impl;

import com.alibaba.fastjson.JSON;
import edu.neu.swc.skynet.constant.AuthorityConstant;
import edu.neu.swc.skynet.constant.CommonConstant;
import edu.neu.swc.skynet.dao.SkyNetUserDao;
import edu.neu.swc.skynet.entity.SkyNetUser;
import edu.neu.swc.skynet.service.IJwtService;
import edu.neu.swc.skynet.vo.LoginUserInfo;
import edu.neu.swc.skynet.vo.UsernameAndPassword;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import sun.misc.BASE64Decoder;

import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.UUID;

/**
 * <h1>JWT 相关服务接口实现</h1>
 * 针对 IJwtService 接口的一种实现
 */
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class JwtServiceImpl implements IJwtService {

    private final SkyNetUserDao skyNetUserDao;

    public JwtServiceImpl(SkyNetUserDao skyNetUserDao) {
        this.skyNetUserDao = skyNetUserDao;
    }

    @Override
    public String generateToken(String username, String password) throws Exception {
        return generateToken(username,password,0);
    }

    @Override
    public String generateToken(String username, String password, int expire) throws Exception {

        //1.验证用户是否能够通过授权校验，及输入的用户名和密码能否匹配数据表记录
        log.info("username and password is[{}],[{}]", username,password);
        SkyNetUser skyNetUser = skyNetUserDao.findByUsernameAndPassword(
                username,password
        );

        if(null == skyNetUser){
            log.error("can not find user:[{}],[{}]",username,password);
            return null;
        }

        //2.Token中塞入对象，即JWT中存储的信息，后端拿到这些信息就知道是哪个用户在操作
        LoginUserInfo loginUserInfo = new LoginUserInfo(
                skyNetUser.getId(), skyNetUser.getUsername()
        );

        if(expire <= 0){
            expire = AuthorityConstant.DEFAULT_EXPIRE_DAY;
        }

        //3. 计算超时时间
        ZonedDateTime zdt = LocalDate.now().plus(expire, ChronoUnit.DAYS)
                .atStartOfDay(ZoneId.systemDefault());
        Date expireDate = Date.from(zdt.toInstant());

        //4. 构建 JWT token 对象，使用builder模式
        String s = Jwts.builder()
                //jwt-pay-load
                .claim(CommonConstant.JWT_USER_INFO_KEY, JSON.toJSONString(loginUserInfo))
                // jwt id
                .setId(UUID.randomUUID().toString())
                //jwt 过期时间
                .setExpiration(expireDate)
                //jwt 签名
                .signWith(getPrivateKey(), SignatureAlgorithm.RS256)
                .compact();

        log.info("the token is :[{}]" ,s);
        return s;
    }

    @Override
    public String registerUserAndGenerateToken(UsernameAndPassword usernameAndPassword) throws Exception {

        //1.校验用户名是否存在，如果存在，不能重复注册
        SkyNetUser oldUser = skyNetUserDao.findByUsername(usernameAndPassword.getUsername());
        if(null != oldUser){
            log.error("username is registered [{}]", oldUser.getUsername());
            return null;
        }
        SkyNetUser skyNetUser = new SkyNetUser();
        skyNetUser.setUsername(usernameAndPassword.getUsername());
        skyNetUser.setPassword(usernameAndPassword.getPassword()); // 经过MD5编码以后的密码
        skyNetUser.setExtraInfo("{}");

        //2. 注册一个新用户，写一条记录到数据表中
        skyNetUser = skyNetUserDao.save(skyNetUser);
        log.info("register user success[{}],[{}]",
                skyNetUser.getUsername(),
                skyNetUser.getId());

        return generateToken(skyNetUser.getUsername(),
                skyNetUser.getPassword());
    }

    /**
     * <h2>根据本地存储的私钥获取到 private key 对象</h2>
     * @return 私钥 PrivateKey 对象
     * @throws Exception {InvalidKeySpecException，IOException}
     */
    private PrivateKey getPrivateKey() throws Exception{
        PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(
                new BASE64Decoder().decodeBuffer(AuthorityConstant.PRIVATE_KEY)
        );
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePrivate(priPKCS8);
    }
}
